package com.xiaobai.controller;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xiaobai.common.HotPutObj;
import com.xiaobai.common.ServerItemResult;
import com.xiaobai.common.ServiceResponse;
import com.xiaobai.dao.StudentUserDao;
import com.xiaobai.mybatis_plus.service.IIQualityService;
import com.xiaobai.pojo.StudentUser;
import com.xiaobai.service.achievement.IAchievementService;
import com.xiaobai.service.achievement.v2.IAchievementServiceV2;
import com.xiaobai.service.course.ICourseService;
import com.xiaobai.service.quality.IQualityService;
import com.xiaobai.service.user.IUserService;
import com.xiaobai.units.HttpUnits;
import com.xiaobai.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.logging.log4j.util.Base64Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.text.ParseException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {

    @Autowired
    private IUserService userService;

    @Autowired
    private ICourseService courseService;

    @Autowired
    private IQualityService qualityService;

    @Autowired
    private IAchievementService achievementService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private StudentUserDao studentUserDao;

    @Autowired
    private QualityController qualityController;

    @Autowired
    private AchievementController achievementController;

    @Autowired
    private CourseController courseController;

    @Autowired
    private IIQualityService qualityService_v2;

    @Autowired // 非 mybatis-plus 的 server
    private IAchievementServiceV2 achievementServiceV2;

    private final String wxLoginUrl="https://api.weixin.qq.com/sns/jscode2session?appid=wxa4f2ab589e12dc95&secret=8e75a462b4cc955d7e391159226e24b6&js_code=%s&grant_type=authorization_code";

    private static final Base64.Decoder decoder = Base64.getDecoder();

    /**
     * 这里是用户输入学号密码登录时,并且通过教务系统的认证,假如student表里没有对应的学号,则初始化一切数据
     */
    @PostMapping("/wxFirstLogin")
    @Transactional//异步注解全关了,初始化不开启异步,方便事务回滚!
    public ServiceResponse wxFirstLogin(@RequestParam("code") String code,@RequestParam("student_id") String student_id,
                                        @RequestParam("student_password") String student_password){

        if(student_id.equals("111111")){
            //游客登录
            return ServiceResponse.createBySuccess(visteStart());
        }
        log.info("login学号:"+student_id);
        String openId="weiXin";
        long startTime = System.currentTimeMillis();

        Calendar calendar = Calendar.getInstance();
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        if (hour >= 23 || hour <= 6) {
            return ServiceResponse.fail("教务官网半夜不开放");
        }

        /**
         * 在调用login方法登录
         * 还要查出课表+素拓+成绩 异步存入并且返回wx  data
         */

        student_password= Base64Util.encode(student_password);//按照学校的加密方式加密

        String jsessionid=userService.login(student_id,student_password);

        boolean flag=false;
        String password=studentUserDao.selectStudentPassword(student_id);
        if(!StringUtils.isBlank(password)){
            flag=true;//此用户已经存在
            studentUserDao.updateStudentPassword(student_id,student_password);//无论如何都修改下密码
        }

        /**
         * 用户表
         */
        if(!flag) {//用户表中不存在就注册
            String student_name = null;
            try {
                student_name = userService.getName(jsessionid);
            }catch (Exception e) {
                e.printStackTrace();
                log.info("获取用户姓名异常");
                student_name = "未知";
            }
            StudentUser studentUser = new StudentUser();
            studentUser.setCreatedTime(new Date());
            studentUser.setStudentId(student_id);
            studentUser.setStudentName(student_name);
            studentUser.setStudentOpenid(openId);
            studentUser.setStudentPassword(student_password);
            //填充完毕 准备装入数据库
            userService.addStudentUser(studentUser);
        }

        //开始设置后续身份凭证
        String uuid= UUID.randomUUID().toString();
        /**
         * 存入过滤器凭证
         * weiXin@适配遗留问题
         */
        stringRedisTemplate.opsForValue().set("security:"+student_id,"weiXin@"+uuid,365, TimeUnit.DAYS);

        AllCourseAllList courseJSON=null;
        AllQuality qualityJSON=null;
        List<List<AchievementItem>> achievementJSON =null;

        // 必定会去官网查询
        flag = false;
        if(!flag) {//用户不存在,即第一次登录,全部去官网查一遍即可
            /**
             * 课程表
             */
            courseJSON = courseService.getAllCourse(student_id, jsessionid);

            /**
             * 素拓表
             */
            qualityJSON = qualityService.allQualityJSON(jsessionid, student_id);

            /**
             * 成绩表
             */
//            achievementJSON = achievementService.firstSelectAchievement(jsessionid, student_id);
            achievementJSON = achievementServiceV2.allAchievementByReq(jsessionid, student_id);
        }
        // 第一次login, 应该直接去官网更新数据!!!~~
        // 下面就不要了, flag不会让它进去
        if(flag){//用户存在,则去数据库查

            courseJSON = courseService.getAllCourseByDb(student_id);

            //新学期我会删除课表,这里为了更新课表用
            if(courseJSON==null){//新学期时上课任务的课表会先更新,所以用上课任务更新法
                courseJSON = courseService.getAllCourse(student_id, jsessionid);
            }

            qualityJSON = qualityService.allQualityJSONByDB(student_id);

//            achievementJSON = achievementService.selectAchievementByDB(student_id);
            achievementJSON = achievementServiceV2.allAchievementByDb(student_id);

        }

        /**
         * 传回三个表数据+uuid+openid+student_id
         */
        Map<String,Object> map=new HashMap<>();
        map.put("openid",openId);
        map.put("uuid",uuid);
        map.put("student_id",student_id);


        map.put("courseJSON",courseJSON);

        map.put("qualityJSON",qualityJSON.getQualityItemList());


        map.put("achievementJSON",achievementJSON);

        map.put("jsessionid",jsessionid);

        log.info("首次登录用时:"+ (System.currentTimeMillis() - startTime) + "ms");

        return ServiceResponse.createBySuccess(map);
    }

    private Map<String,Object> visteStart(){

        AllCourseAllList courseJSON;
        AllQuality qualityJSON;
        List<List<AchievementItem>> achievementJSON;

        courseJSON = courseService.getAllCourseByDb("111111");

        qualityJSON = qualityService.allQualityJSONByDB("111111");

        achievementJSON = achievementServiceV2.allAchievementByDb("111111");

        Map<String,Object> map=new HashMap<>();
        map.put("openid","00");
        map.put("uuid","00");
        map.put("student_id","111111");


        map.put("courseJSON",courseJSON);

        map.put("qualityJSON",qualityJSON.getQualityItemList());


        map.put("achievementJSON",achievementJSON);

        return map;
    }

    /**
     * 普通热加载 (非首次登录,微信已经有了缓存)
     * 就是为了获取jsessionid
     * 热加载后就是更新
     */
    @GetMapping("/ordinaryLogin")
    public ServiceResponse ordinaryLogin(String student_id){

        long startTime = System.currentTimeMillis();
        ServiceResponse cc = this.cc(student_id);

        if (!cc.isSuccess()) {
            return cc;
        }

        String jsessionId = cc.getMsg();
        // 查询 课表 + 素拓 + 成绩 全部一起查了算了(因为补考)

        // 素拓
        ServiceResponse serviceResponse = qualityController.selectNewQuality(student_id, jsessionId);
        Object qa = serviceResponse.getData();

        // 成绩
//        ServiceResponse serviceResponse1 = achievementController.selectNewAcAndGxk(student_id, jsessionId);
//        ServiceResponse serviceResponse2 = achievementController.selectNewAchievement(student_id, jsessionId);
//        Object gxk = serviceResponse1;
//        Object zyk = serviceResponse2;
        List<List<AchievementItem>> achievementList = achievementServiceV2.allAchievementByReq(jsessionId, student_id);

        // 课表
        ServiceResponse serviceResponse3 = courseController.selectNewCourse(student_id, jsessionId);
        Object kcb = serviceResponse3.getData();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("qa", qa);
//        jsonObject.put("gxk", gxk);
//        jsonObject.put("zyk", zyk);
        jsonObject.put("ac", achievementList);
        jsonObject.put("kcb", kcb);

        HotPutObj hotPutObj = new HotPutObj();
        hotPutObj.setCode(0);
        hotPutObj.setMsg(jsessionId);
        hotPutObj.setJsonObject(jsonObject);
        log.info("全局更新用时:" + (System.currentTimeMillis() - startTime) +"ms");

        return ServiceResponse.createBySuccess(hotPutObj);
    }

    /**
     * 主动更新 摇一摇 二选一[素拓分, 成绩]
     * 0: 素拓分
     * 1: 成绩
     */
    @GetMapping("/updateItem")
    public ServiceResponse updateItem(String studentId, Integer type){

        long startTime = System.currentTimeMillis();
        ServiceResponse cc = this.cc(studentId);
        if (!cc.isSuccess()) {
            return cc;
        }

        String jsessionId = cc.getMsg();

        Map<String, Object> map = new HashMap<>(2);

        if (type == 0) {
            // 素拓
            ServiceResponse<List<QualityItem>> serviceResponse = qualityController.selectNewQuality(studentId, jsessionId);
            List<QualityItem> qa = serviceResponse.getData();
            map.put("qa", qa);
            log.info("素拓长度:" + qa.size());
            log.info("素拓更新用时:" + (System.currentTimeMillis() - startTime) +"ms");
        } else if (type == 1) {
            List<List<AchievementItem>> lists = achievementServiceV2.allAchievementByReq(jsessionId, studentId);

            JSONArray jsonArray = new JSONArray(0);
            /**
             * todo 非期末时期不要开启此flag 通知响应很耗时
             */
            if (false) {
                jsonArray = this.putNotice(jsessionId);
            }

            Date lastTime = new Date();
            List<String> noticeList = new ArrayList<>();
            for(int i = 0; i < jsonArray.size(); i++) {
                JSONObject item = jsonArray.getJSONObject(i);
                // 计算与上一个的时间跨度, 跨度超过2个月就break了(最新的通知)

                Date thisTime = null;
                try {
                    thisTime = DateUtils.parseDate(item.getString("cjsj"), "yyyy-MM-dd HH:mm:ss");
                } catch (ParseException e) {
                    e.printStackTrace();
                    continue;
                }

                if (DateUtils.addDays(thisTime, 60).getTime() < lastTime.getTime()) {
                    // 当前这里的成绩,距离最新的已经超过2个月,说明是上学期的成绩了
                    break;
                }
                lastTime = thisTime;
                String msg = item.getString("msg");
                String kcmc = JSONObject.parseObject(msg).getString("kcmc");
                boolean isAdd = true;

                // 获取最后一个学期的成绩, 就是当前最想念学期
                List<AchievementItem> achievementItems = lists.get(lists.size() - 1);
                for (AchievementItem achievementItem : achievementItems) {
                    if (achievementItem.getKcmc().equals(kcmc)) {
                        isAdd = false;
                        break;
                    }
                }

                if (isAdd) {
                    noticeList.add(msg);
                }
            }
            log.info("成绩更新用时:" + (System.currentTimeMillis() - startTime) +"ms");
            map.put("notice", noticeList);
            map.put("ac", lists);
        }else {
            return null;
        }

        return ServiceResponse.createBySuccess(map);
    }

    // 密码是否正确
    private ServiceResponse cc(String student_id) {

        Calendar calendar = Calendar.getInstance();
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        int min = calendar.get(Calendar.MINUTE);
        if (hour >= 23 || hour <= 6 || (hour == 22 && min >= 50)) {
            return ServiceResponse.fail(new HotPutObj(5, "教务网休息了", null));
        }

        final String lockKeyPre = "jsession_lock:";
        final String valueKeyPre = "jsession:";

        // 不存在则锁住
        Boolean ok = stringRedisTemplate.opsForValue()
                .setIfAbsent(lockKeyPre + student_id, "is lock", 3, TimeUnit.SECONDS);

        if (!ok) {
            // 前面已经有请求去执行了, 请等等
            log.info("等待获取前面的JSESSIONID");
            // 至多等待2秒钟
            for (int i = 0; i < 4; i++) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String jsessionId = stringRedisTemplate.opsForValue().get(valueKeyPre + student_id);
                if (jsessionId != null) {
                    log.info("已获取前一个JSESSIONID");
                    return ServiceResponse.createBySuccessMsg(jsessionId);
                }
                // 循环: 继续等待
            }
            // 等待过久, 前一个还没有执行完毕, 那么这里可以出发了。
            // 正常流程的bug是最多只会同时来两个请求, 所以这里可以直接让它出发, 不继续做判断
        }

        log.info("更新学号:"+student_id);

        String student_password=userService.selectStudentPassword(student_id);

        byte[] decode = decoder.decode(student_password);

        ServerItemResult serverItemResult = HttpUnits.loginPost_V2(student_id, new String(decode));
        if (!serverItemResult.isFlag()) {
            // 获取jsession失败
            if (serverItemResult.getMsg().startsWith("密码错误")) {
                return ServiceResponse.fail(new HotPutObj(9, serverItemResult.getMsg(), null));
            }
            return ServiceResponse.fail(new HotPutObj(5, serverItemResult.getMsg(), null));
        }
        String jsessionID = serverItemResult.getMsg();
        // 缓存起来, 下次还用
        stringRedisTemplate.opsForValue().set(valueKeyPre + student_id, jsessionID, 40, TimeUnit.SECONDS);
        return ServiceResponse.createBySuccessMsg(serverItemResult.getMsg());
    }

    /**
     * 拉取通知
     */
    private JSONArray putNotice(String jsessionId) {
        Map<String, String> noticeMap = new HashMap<>(4);
        noticeMap.put("page", "1");
        noticeMap.put("row", "15");
        noticeMap.put("sort", "cjsj");
        noticeMap.put("order", "desc");
        String noticeContent = HttpUnits.gyPost("https://jwxt.gdupt.edu.cn/xxtz!getDataList.action", jsessionId, noticeMap);
        JSONObject jsonObject = JSONObject.parseObject(noticeContent);
        JSONArray jsonArray = jsonObject.getJSONArray("rows");
        return jsonArray;
    }

}
